#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Purpose: Field view based on Traits UI
# Created: 03/19/2009
# $Id $
# Copyright (c) 2008,2009 by University College London
# Authors:
# Didrik Pinte --<dpinte@dipole-consulting.com>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with HydroPlatform for details.


import logging
import numpy

# ETS imports
from enthought.enable.component_editor import ComponentEditor


# Traits imports
from enthought.traits.api import adapts, Bool, Int, CTrait, Constant,\
     on_trait_change

from enthought.traits.ui.api import View, ArrayEditor, Item, HGroup, \
     ListEditor, TableEditor, TabularEditor, Handler, ITreeNode, \
     ITreeNodeAdapter, EnumEditor, Label, HSplit, InstanceEditor, spring, \
     CheckListEditor

from enthought.traits.ui.editors.array_editor import SimpleEditor
from enthought.traits.ui.table_column import ObjectColumn
from enthought.traits.ui.menu import OKButton, CancelButton

from enthought.traits.ui.api import TreeEditor, TreeNode, View, Item, VSplit, \
           HGroup, Handler, Group
from enthought.traits.ui.menu import Menu, Action, Separator
from enthought.traits.ui.wx.tree_editor import NewAction, CopyAction, \
     CutAction, PasteAction, DeleteAction, RenameAction
from enthought.traits.ui.tabular_adapter import TabularAdapter,\
     AnITabularAdapter
from enthought.traits.ui.ui_editors.array_view_editor import ArrayViewEditor
from enthought.traits.ui.key_bindings import KeyBinding, KeyBindings

# Chaco imports
from enthought.chaco.chaco_plot_editor import ChacoPlotItem


from hydroplatform.model.fields import *
from hydroplatform.model import network



# Field editor

class DataGroup(HasTraits):
    '''
    A DataGroup is the trait version of the FieldGroup. It holds the following
     lists :
     - a list of Parameters
     - a list of SeasonalParameters
     - a list of TimeSerie
     - a list of Tables
     
    TODO : this is not really a good design and should allow using a list of 
    Field object
    '''
    name = Str('<unknown>')
    parameters = List(Parameter)
    sparameters = List(SeasonalParameter)
    timeseries = List(TimeSerie)
    tables = List(Table)
    nodereferences = List(NodeReferences)
    linkreferences = List(LinkReferences)

# View for objects that aren't edited
no_view = View(width=-500)
    
# Actions used by tree editor context menu
# FIXME : this is not really used in the app, should be removed
parameter_action = Action(
    name='Parameters',
    action='handler.parameter_group(editor,object)')
    
class TreeHandler ( Handler ):

    def parameter_group ( self, editor, object ):
        group = editor.get_parent( object )
        print '%s belongs to the %s group.' %\
            ( object.name, group.name )


'''
View used in the TableEditor when a parameter is selected.
It is also used in the TreeEditorParameterAdapter class.

Basic view allowing only to edit the data part of the Parameter
'''
edit_parameter_view = View( VGroup(Item(name="name", style='readonly'),
                               Item(name="data"), 
                               Item(name="unit", style="readonly")),
                     dock = 'vertical',
                     width=-500) 

'''
Table editor for a list of Parameters
'''
parameters_editor = TableEditor(
    columns = [ObjectColumn(name='name', style="readonly"),
               ObjectColumn(name='data'),
               ObjectColumn(name='unit', style="readonly")],
    auto_size= True,
    editable=False,
    edit_view=edit_parameter_view, 
    edit_view_height=0.2,
    deletable=False,
    orientation = 'vertical',
    selection_color = 'coral',
    selection_bg_color = 'white',
    sortable=True
)

def get_noderef_editor(context):
    """
    Returns a nodereference editor based on the list of nodes available 
    in the loaded network
    """
    net = context.session.hpnetwork
    vlist = list()
    for node in net.nodes:
        vlist.append((node.id,node.name,))
    
    
    simple_view =  View( VGroup(Item(name="name"),
#                               Item(name="data",
#                                    style="custom", 
#                                    editor=CheckListEditor(cols=3, values=vlist),
#                                    defined_when="single is False"),
#                               Item(name="data",
#                                    style="simple", 
#                                    editor=CheckListEditor(cols=3, values=vlist),
#                                    defined_when="single is True")
                               ), 
                     dock = 'vertical',
                     width=-500) 
    editor = TableEditor(
        columns = [ObjectColumn(name='name', style="readonly"),
                   ObjectColumn(name='data', 
                                style="readonly", 
                                editor=CheckListEditor(cols=3, values=vlist)), 
                   ],
        auto_size = False,
        editable  = True,
        deletable = False,
        edit_view =  simple_view,
        orientation = 'vertical',
        selection_color = 'coral',
        selection_bg_color = 'white',
        sortable=True
    )    
    return editor

def get_linkref_editor(context):
    """
    Returns a linkreference editor based on the list of nodes available 
    in the loaded network
    """
    net = context.session.hpnetwork
    vlist = list()
    for link in net.links:
        vlist.append((link.id,link.name,))
        
    simple_view =  View( VGroup(Item(name="name"),
#                                Item(name="data",
#                                    style="custom", 
#                                    editor=CheckListEditor(cols=3, values=vlist),
#                                    defined_when="single is False"),
#                               Item(name="data",
#                                    style="simple", 
#                                    editor=CheckListEditor(cols=3, values=vlist),
#                                    defined_when="single is True")
                               ),
                     dock = 'vertical',
                     width=-500) 
    editor = TableEditor(
        columns = [ObjectColumn(name='name', style="readonly"),
                   ObjectColumn(name='data', style="readonly", 
                                editor=CheckListEditor(cols=3, values=vlist)), 
                   ],
        auto_size = False,
        editable  = True,
        deletable = False,
        edit_view =  simple_view,
        orientation = 'vertical',
        selection_color = 'coral',
        selection_bg_color = 'white',
        sortable=True
    )    
    return editor
    

def get_objectlist(adaptee, obj_type, hpnetwork, dbsession):
    filter_obj = False
    cfilter = []
    if adaptee.class_filter is not "":
        cname = adaptee.class_filter.split(",")
        for name in cname:
            obj = dbsession.query(obj_type).filter_by(name=name).first()
            if obj is not None:
                cfilter.append(obj.name)
    if len(cfilter) > 0:
        filter_obj = True
        
    vlist = list()
    for node in hpnetwork.nodes:
        if filter_obj is True:
            if node.type.name in cfilter:
                vlist.append((node.id, node.name,))
        else:
            vlist.append((node.id, node.name,))
        
    # appends the non visible object too
    for nodetype in hpnetwork.project.nodetypes:
        if nodetype.visible_on_map is False:
            for node in nodetype.nodes:                
                if filter_obj is True:
                    if node.type.name in cfilter:
                        vlist.append((node.id, node.name,))
                else:
                    vlist.append((node.id, node.name,))    
    return vlist

def get_objectlist_links(adaptee, obj_type, hpnetwork, dbsession):
    filter_obj = False
    cfilter = []
    if adaptee.class_filter is not "":
        cname = adaptee.class_filter.split(",")
        for name in cname:
            obj = dbsession.query(obj_type).filter_by(name=name).first()
            if obj is not None:
                cfilter.append(obj.name)
    if len(cfilter) > 0:
        filter_obj = True
        
    vlist = list()
    for link in hpnetwork.links:
        if filter_obj is True:
            if link.type.name in cfilter:
                vlist.append((link.id, link.name,))
        else:
            vlist.append((link.id, link.name,))
        
    # appends the non visible object too
#    for linktype in hpnetwork.project.linktypes:
#        if linktype.visible_on_map is False:
#            for link in linktype.links:                
#                if filter_obj is True:
#                    if link.type.name in cfilter:
#                        vlist.append((link.id, link.name,))
#                else:
#                    vlist.append((link.id, link.name,))    
    return vlist


"""
Dirty sharing of the context for access using an ITreeNodeAdapter ...
"""
shared_context = None
class TreeEditorNodeRerefencesAdapter(ITreeNodeAdapter):
    """
    Adapter to allow NodeReference to be see in the TreeEditor
    """
    
    adapts(NodeReferences, ITreeNode)
    
    def get_label(self):
        return self.adaptee.name
    
    def get_menu(self):
        return False
    
    def get_view(self):
        global shared_context
        if shared_context is not None:
            hpnetwork = shared_context.session.hpnetwork
            dbsession = shared_context.session.dbsession
            
            vlist = get_objectlist(self.adaptee, network.NodeType, \
                                   hpnetwork, dbsession)    
            if len(self.adaptee.data) == 0 and len(vlist) > 0:
                self.adaptee.data = [vlist[0][0],]
                
            return View( VGroup(Item(name="name"),
                                 Item(name="data",
                                    style="custom", 
                                    editor=CheckListEditor(cols=3, values=vlist),
                                    defined_when="single is False"),
                               Item(name="data",
                                    style="simple", 
                                    editor=CheckListEditor(cols=3, values=vlist),
                                    defined_when="single is True")
                               ),
                     dock = 'vertical',
                 width=-500) 
        return no_view
    
    
class TreeEditorLinkRerefencesAdapter(ITreeNodeAdapter):
    """
    Adapter to allow LinkReference to be seen in the TreeEditor
    """
    
    adapts(LinkReferences, ITreeNode)
    
    def get_label(self):
        return self.adaptee.name
    
    def get_menu(self):
        return False
    
    def get_view(self):
        global shared_context
        if shared_context is not None:
            hpnetwork = shared_context.session.hpnetwork
            dbsession = shared_context.session.dbsession
            
            vlist = get_objectlist_links(self.adaptee, network.LinkType, \
                                   hpnetwork, dbsession)
            if len(self.adaptee.data) == 0 and len(vlist) > 0:
                self.adaptee.data = [vlist[0][0],]
        
            return View( VGroup(Item(name="name"),
                                 Item(name="data",
                                    style="custom", 
                                    editor=CheckListEditor(cols=3, values=vlist),
                                    defined_when="single is False"),
                               Item(name="data",
                                    style="simple", 
                                    editor=CheckListEditor(cols=3, values=vlist),
                                    defined_when="single is True")
                               ),
                     dock = 'vertical',
                 width=-500) 
        return no_view


class TreeEditorNodeRerefenceAdapter(ITreeNodeAdapter):
    """
    Adapter to allow NodeReference to be seen in the TreeEditor
    """
    
    adapts(NodeReference, ITreeNode)
    
    def get_label(self):
        return self.adaptee.name
    
    def get_menu(self):
        return False
    
    def get_view(self):
        return no_view
    
    
class TreeEditorLinkRerefenceAdapter(ITreeNodeAdapter):
    """
    Adapter to allow LinkReference to be see in the TreeEditor
    """
    
    adapts(LinkReference, ITreeNode)
    
    def get_label(self):
        return self.adaptee.name
    
    def get_menu(self):
        return False
    
    def get_view(self):
        return no_view
    
    
class TreeEditorParameterAdapter(ITreeNodeAdapter):
    '''
    Adapter to allow Parameters to be seen in TreeEditor
    '''
    
    adapts(Parameter, ITreeNode)
    
    def get_label(self):
        return self.adaptee.name
    
    def get_menu(self):
        return False
    
    def get_view(self):
        '''
        Returns a simple view allowing to edit only the data
        '''
        return edit_parameter_view
 
    
# Seasonal parameter view
class SPAdapter(TabularAdapter):
    '''
    Seasonal parameter adapter to be shown in a TabularEditor
    '''
    columns = [('date', 'date'), ('value', 0)]
    alignment = 'right'
    
    value_can_edit = True
    
    date_alignment = Constant("left")
    date_format = "%s"
    date_can_edit = False
    date_text = Property
    
    def _get_date_text(self):
        '''
        Returns the date value as text
        '''
        try:
            return self.object.dates[self.row]
        except Exception, e:
            logging.exception(e)
            return u""
    
    def _get_default_value(self, object, trait):
        '''
        Returns an empty row for the seasonnal data filled with NAN's
        '''
        #NAN changed to NaN (case sensitive)
        return numpy.empty((1, 1)) * numpy.NaN
    
    def insert ( self, object, trait, row, value ):
        """ 
        Overloading the default insert method
        
        FIXME : is this used ? The seasonnal dataset size is fixed by the 
        dates
        """
        self.object.data = numpy.insert(self.object.data, row, value, 0)
    
    
    def set_text ( self, object, trait, row, column, text ):
        """ 
        Overloads the set_text method of the TabularAdapter to track changes to
        data
        """
        self.object._updated = True        
        try:
            TabularAdapter.set_text(self, object, trait, row, column, text)
        except ValueError, e:
            # the ValueError is commonly raised when the user sets the value 
            # with a sequence in place of a text value
            logging.warn(e)
        
class TreeEditorSPAdapter(ITreeNodeAdapter):
    '''
    Tree adapter for the SeasonalParameter class
    '''
    
    adapts(SeasonalParameter, ITreeNode)
    
    def get_label(self):
        '''
        Leaf name in the tree control
        '''
        return self.adaptee.name
    
    def has_children(self):
        '''
        Has no children
        '''
        return False
    
    def allows_children(self):
        '''
        Do not allow children
        '''
        return False

    def get_view(self):
        '''
        The TabularEditor cannot do insert or append because the TS is locked 
        on the dates
        
        FIXME : should support pasting !
        '''
        return View(VGroup(Item('data', 
                                  show_label=False,
                                  editor=TabularEditor(adapter=SPAdapter(), 
                                             editable_labels=False,
                                             editable=True,
                                             operations=['edit'])),
                                  Item("paste_btn", show_label=False)
                      ),
                      key_bindings = self.adaptee.spkeybindings,
                      title        = "Seasonal parameter editor",
                      width        = -500,
                      resizable=True)
    
# Timeserie view
class TSAdapter(TabularAdapter):
    '''
    TabularEditor adapter for TimeSerie class
    '''
    columns = [('date', 'date'), ('value', 0)]
    alignment = 'right'    
    

    
    value_can_edit = True
    
    date_alignment = Constant("left")
    date_format = "%s"
    date_can_edit = False
    date_text = Property
    
    def _get_date_text(self):
        '''
        Return the date value as text for the given row
        '''
        return self.object.dates[self.row]
    
    def _get_default_value(self, object, trait):
        '''
        Default value for a new row. Filled of NAN's
        '''
        #NAN changed to NaN
        return numpy.empty((1, 1)) * numpy.NaN
    
    def insert ( self, object, trait, row, value ):
        """ 
        Overloading the default insert method
        
        FIXME : is this method needed ? Shape is known and cnnot be increased 
        or decreased ...
        """
        self.object.data = numpy.insert(self.object.data, row, value, 0)
    
    
    def set_text ( self, object, trait, row, column, text ):
        """ 
        Overloads the set_text method of the TabularAdapter to track changes to
        data
        """
        self.object._updated = True        
        try:
            TabularAdapter.set_text(self, object, trait, row, column, text)
        except ValueError, e:
            # the ValueError is commonly raised when the user sets the value 
            # with a sequence in place of a text value
            logging.warn(e)
        
        # FIXME : this could be really costly when updating huge graphics ...
        # FIXME : refreshing the plot two times ? refresh_plot is linked to 
        # _updated changes
        self.object.refresh_plot()    
        

class TreeEditorTSAdapter(ITreeNodeAdapter):
    '''
    Tree editor adapter for TimeSerie class
    '''
    
    adapts(TimeSerie, ITreeNode)
    
    def get_label(self):
        '''
        Returns TimeSerie name
        '''
        return self.adaptee.name
    
    def has_children(self):
        '''
        Has no children !
        '''
        return False
    
    def allows_children(self):
        '''
        Children not allowed !
        '''
        return False

    def get_view(self):
        '''
        The TabularEditor cannot do insert or append because the TS is locked 
        on the dates
        '''
        return View(HSplit(VGroup(
                                Item('data', 
                                    show_label=False,
                                    editor=TabularEditor(adapter=TSAdapter(), 
                                             editable=True,
                                             editable_labels=False,
                                             operations=['edit'])),
                                Item("paste_btn", show_label=False)
                            ),
                            Item('ts_plot', editor=ComponentEditor(), 
                                 show_label=False)
                          ),
                      key_bindings = self.adaptee.tskeybindings,
                      title        = "Time series editor",
                      width        = -500,
                      resizable=True)
# Table view
class TableAdapter(TabularAdapter):
    '''
    Tabular editor adapter for Table class
    '''
    
    def _get_default_value(self, object, trait):
        '''
        Default value is a row of NAN's value matching the object.data shape
        '''
        #NAN changed to NaN (case sensitive!!)
        return numpy.empty((1, len(self.columns))) * numpy.NaN
    
    def insert ( self, object, trait, row, value ):
        """ 
        Overloading the default insert method : insert adds a line at the
        given position in the matrix. The value is not to be used but a
        NAN row must be added
        
        TODO : numpy.append() and numpy.insert() are not always reliable 
              (dixit Robert Kern, 31 March 2009, enthought-dev mailing list)
                            
        """
        #NAN changed to NaN 
        newrow = numpy.empty( (1, self.object.data.shape[1])) * numpy.NaN
        self.object.data = numpy.insert(self.object.data, row, newrow , 0)
        
    def delete (self, object, trait, row):
        """
        Delete removes a line from the numpy array
        """
        self.object.data = numpy.vstack((self.object.data[:row], self.object.data[row+1:]))
    
    
    def set_text ( self, object, trait, row, column, text ):
        """ 
        Overloads the set_text method of the TabularAdapter to track changes to
        data
        """
        try:
            TabularAdapter.set_text(self, object, trait, row, column, text)
        except ValueError, e:
            # the ValueError is commonly raised when the user sets the value 
            # with a sequence in place of a text value
            logging.warn(e)
            
        self.object._updated = True        
        # FIXME : this could be really costly when updating huge graphics ...
        # FIXME : refreshing the plot two times ? refresh_plot is linked to 
        # _updated changes --> self.object._update = True should probably be 
        # called after the set_text() call
        self.object.refresh_plot()
        
class TreeTableAdapter( ITreeNodeAdapter):
    '''
    Adapts a hydroplatform.model.field.Table object into something
    usable by a TreeEditor
    '''
    
    adapts(Table, ITreeNode)
    
    def get_label(self):
        '''
        Return Table name
        '''
        return self.adaptee.name
    
    def has_children(self):
        '''
        Has no children !
        '''
        return False
    
    def allows_children(self):
        '''
        Children not allowed !
        '''
        return False
    
    def get_view(self):
        '''create a TabularAdapter with the columns from the 
           Table.colLabels property
        '''
        if len(self.adaptee.columns) == 0:
            return View(Label('No columns set for this table'))
        tblAdapter = TableAdapter(columns = self.adaptee.colLabels)
        return View(HSplit(VGroup(Item('data',
                                show_label = False,
                                editor     = TabularEditor(adapter=tblAdapter,
                                                           editable=True,
                                                           editable_labels=True),
                                ),                                
                                  Item('paste_btn', show_label=False),
                            ),
                            VGroup(
                                VGroup(Item("xcol", width=100), 
                                       Item("ycol", width=100)),
                                 Item('array_plot', editor=ComponentEditor(),
                                     show_label = False)
                                )
                            ),
                    key_bindings = self.adaptee.tablekeybindings,
                    title = "Table editor",
                    width = -500,
                    resizable = True)
    
# ObjectEditorDialog tree editor
class FieldValueEditorTree(HasTraits):
    '''
    Base class managing the tree control and editor in the ObjectEditorDialog
    
    Dynamically construct the list of nodes definition based on the content of 
    the DataGroup. This is needed to prevent showing empty groups in the tree 
    control
    '''
    name = Str('<unknown>')
    group = Instance(DataGroup)
    
    def get_editor_view(self, context):
        '''
        Returns the View with a dynamically configure TreeEditor
        '''
        global shared_context
        shared_context = context
        nodes = [TreeNode(node_for=[DataGroup],
                          auto_open=True,
                          children="",
                          label="name",
                          menu = False,
                          view = no_view)
                 ]
        if len(self.group.parameters) > 0:
            nodes.append(TreeNode( node_for  = [ DataGroup ],
                  auto_open = True,
                  children  = 'parameters',
                  label     = '=Parameters',
                  view      = View( Group (Item('parameters', 
                                                show_label=False, 
                                                editor=parameters_editor),
                                   orientation='vertical',
                                   show_left=True ),
                                   width=500
                                   ),
                  menu      = False
                  ))
        if len(self.group.sparameters) > 0:
            nodes.append(TreeNode( node_for  = [ DataGroup ],
                  auto_open = True,
                  children  = 'sparameters',
                  label     = '=Seasonal parameters',
                  view      = no_view,
                  menu      = False
                   ))
 
        if len(self.group.timeseries)>0:
            nodes.append(TreeNode( node_for  = [ DataGroup ],
                  auto_open = True,
                  children  = 'timeseries',
                  label     = '=Time series',
                  view      = no_view,
                  menu      = False
            ))
        if len(self.group.tables) > 0:
            nodes.append(TreeNode( node_for  = [ DataGroup ],
                  auto_open = True,
                  children  = 'tables',
                  label     = '=Tables',
                  view      = no_view,
                  menu      = False
                   ))
        if len(self.group.nodereferences) > 0:
            # MARK
            # FIXME
            # Problem with this, and the clause below, as the list of checkboxes has the first 
            # object ticked by default
            # UPDATE: Problem now seems to be with CheckListEditor in enthought.traits.ui.api
            # UPDATE: Have emailed Didrik, I think he should fix it, no clue whether I can modify
            # enthought libraries.
            nodes.append(TreeNode( node_for = [DataGroup],
                 auto_open = True,
                  children  = 'nodereferences',
                  label     = '=Node references',
                  view      = View( Group (Item('nodereferences', 
                                                show_label=False, 
                                                editor=get_noderef_editor(context)),
                                   orientation='vertical',
                                   show_left=True ),
                                   width=500
                                   ),
                  menu      = False
                  ))
        if len(self.group.linkreferences) > 0:
            nodes.append(TreeNode( node_for = [DataGroup],
                 auto_open = True,
                  children  = 'linkreferences',
                  label     = '=Link references',
                  view      = View( Group (Item('linkreferences', 
                                                show_label=False, 
                                                editor=get_linkref_editor(context)),
                                   orientation='vertical',
                                   show_left=True ),
                                   width=500
                                   ),
                  menu      = False
                  ))
 
            
        tree_editor = TreeEditor(
               hide_root = True,
               nodes = nodes )
        
        return View( 
                Item(
                    name = 'group',
                    editor = tree_editor,
                    id = \
                     'hydroplatform.model.fields.group',
                    resizable = True,
                    width = -150,
                    show_label=False),

                id = \
                 'hydroplatform.model.fields.datagroup_owner',
                dock = 'horizontal',
                drop_class = HasTraits,
                handler = TreeHandler(),
                buttons = [ 'Undo', 'OK', 'Cancel' ],
                resizable = True,
                width = .95,
            )

# Node type editor classes



FIELD_NAME = {
        "" : None,
        "Parameter" : Parameter,
        "Table" : Table,
        "Time-Series" : TimeSerie,
        "Seasonal Parameter" : SeasonalParameter,
        "Node references" : NodeReferences,
        "Link references" : LinkReferences,
        #"Node reference" : NodeReference,
        #"Link reference" : LinkReference
    }

def get_name_for_class(instance):
    '''
    Function used in the ObjectTypeEditor to get the class name 
    of a given instance
    '''
    if instance is None:
        return None
    for key, value in FIELD_NAME.items():  
        if value is None:
            continue
        if isinstance(instance, value):
            return key
    else:
        return None
    
'''
Trait object defining the allowed Field types

TODO : improve this way of statically managing things ...
'''
FIELD_TYPES = Trait("", 
    FIELD_NAME,
    label = "Field type",
    desc = "Type of field content"
)    

class ObjectField(HasTraits):
    '''
    Trait class used to configure a new object field for a selected object type
    
    An object field is defined by a name, a type and its related instance 
    and a group. 
    '''
    fid = Int(-1)
    name = Str
    ftype = FIELD_TYPES
    group = Str()
    parent = Any
    groups = DelegatesTo('parent')
    project = DelegatesTo('parent')
    finst = Instance(Field)
    _configupdated = DelegatesTo('finst', transient=True)
    
    traits_view = View('name', 
                       'ftype', 
                       Item('group', editor=EnumEditor(name='groups'))
                       )
    
    def __init__(self, **kw):
        HasTraits.__init__(self, **kw)
        # Traits Str initialise strings to "" and not to None !
        if self.group == '' and len(self.groups) > 0:
            self.group = self.groups[-1]
   
    def _ftype_changed(self, info):
        '''
        When the user select another field type, create the needed instance
        '''
        if self.traits_inited():
            # only update the type if the traits is really changed 
            # and not only initiated
            if self.ftype_ is not None:
                self.finst = self.ftype_(name=self.name,
                                     fromd = self.project.horizon_from,
                                     tod   = self.project.horizon_to,
                                     step  = self.project.time_step)        
                self._configupdated = True
    
    def _name_changed(self):
        '''
        If name is changed, sets the _updated trait to True. 
        
        Also updates the field instance if any
        '''
        if self.traits_inited():
            if self.finst is not None:
                self._configupdated = True
                self.finst.name = self.name
    
    def _group_changed(self):
        '''
        If the group is changed, sets the _updated trait to True.
        '''
        if self.traits_inited():
            if self.finst is not None:
                self._configupdated = True
    
    def _finst_changed(self):
        '''
        If the field instance is changed, sets the _updated trait to True.
        '''
        if self.traits_inited():
            self._configupdated = True

class FieldTypeColumn(ObjectColumn):
    '''
    Colored line for the field type if not already instantiated
    '''
    
    def get_text_color(self, object):
        return [ u'red', u'black' ][ object.finst is not None ]
        

class ObjectFields(HasTraits):
    '''
    This is the container for the list of fields in the ObjectTypeEditorDialog
    
    It contains a view on the list of ObjectField
    '''
    fields = List(ObjectField)
    groups = ListStr
    project = Instance(network.Project)
    
    def getObjectFieldsView(self):
        '''
        Return a trait.ui View for the given nodefields using a list 
        of available groups
        
        !! The view used by this View is the traits_view for each nodefield 
        type
        
        TODO : fix the tabulation behaviour in the TableEditor that is not 
        really intuitive
        '''        
        nodefields_editor = TableEditor(
            columns = [ObjectColumn(name='name', label="Name"), 
                       FieldTypeColumn(name='ftype', label="Field type"), 
                       ObjectColumn(name='group', label="Group",
                                    editor=EnumEditor(name='groups'))],
            auto_size = False,
            editable  = True,
            edit_on_first_click = False, 
            edit_view = View( 
                Item(name="finst", style='custom', 
                     editor=InstanceEditor(), show_label= False),
                dock = 'vertical'),
            edit_view_height = 200,
            deletable = True,
            orientation = 'vertical',
            selection_mode = "row",
            selection_color = 'coral',
            selection_bg_color = 'white',
            row_factory=ObjectField,        
            row_factory_kw={'parent'  : self, 
                            '_updated': True},
            show_toolbar = True,
            sortable=True)
    
        return  View(Group(
                           Item('fields', editor=nodefields_editor),
                                #show_label=False),
                           spring
                        ),
                       #width = 0.8,                       
                       #height = 0.8,
                       spring,
                       resizable = True
                )
